Esse tutorial te guiará no processo de desligar graciosamente uma aplicação Spring Boot.
A implementação nesse post foi criada originalmente por Andy Wilkinson e adaptada por mim para Spring Boot 2. O código é baseado nesse comentário no GitHub.
Introdução
Muitos desenvolvedores e arquitetos discutem sobre o design de uma aplicação, tráfego, frameworks, design patterns que serão aplicados, mas bem poucos discutem sobre a como desligar a aplicação.
Vamos considerar esse cenário, há uma aplicação com um longo processo sincrono e essa aplicação precisa ser desligada e substituída por uma nova versão. Não seria melhor se ao invés de matar todas as conexões a aplicação esperasse os processos terminarem antes de desligar?
É isso o que nós vamos cobrir nesse tutorial!
Pre-req
- JDK 1.8
- Editor de texto ou sua IDE favorita
- Maven 3.0+
Spring Boot, Tomcat
Para fazer essa funcionalidade funcionar, o primeiro passo é implementar TomcatConnectorCustomizer
.
import org.apache.catalina.connector.Connector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class GracefulShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
private static final Logger log = LoggerFactory.getLogger(GracefulShutdown.class);
private static final int TIMEOUT = 30;
private volatile Connector connector;
@Override
public void customize(Connector connector) {
this.connector = connector;
}
@Override
public void onApplicationEvent(ContextClosedEvent event) {
this.connector.pause();
Executor executor = this.connector.getProtocolHandler().getExecutor();
if (executor instanceof ThreadPoolExecutor) {
try {
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
threadPoolExecutor.shutdown();
if (!threadPoolExecutor.awaitTermination(TIMEOUT, TimeUnit.SECONDS)) {
log.warn("Tomcat thread pool did not shut down gracefully within "
+ TIMEOUT + " seconds. Proceeding with forceful shutdown");
threadPoolExecutor.shutdownNow();
if (!threadPoolExecutor.awaitTermination(TIMEOUT, TimeUnit.SECONDS)) {
log.error("Tomcat thread pool did not terminate");
}
}
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
Na implementação acima o ThreadPoolExecutor
esperará 30
segundos antes de prosseguir com o desligamento, simples, correto?
Agora é necessário registrar esse bean ao contexto da aplicação
e injetá-lo no Tomcat
.
Para isso, apenas crie os seguintes Spring @Bean
s.
@Bean
public GracefulShutdown gracefulShutdown() {
return new GracefulShutdown();
}
@Bean
public ConfigurableServletWebServerFactory webServerFactory(final GracefulShutdown gracefulShutdown) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.addConnectorCustomizers(gracefulShutdown);
return factory;
}
Como testar?
Para testar essa implementação eu criei um LongProcessController
que contem uma Thread.sleep
de 10
segundos.
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LongProcessController {
@RequestMapping("/long-process")
public String pause() throws InterruptedException {
Thread.sleep(10000);
return "Process finished";
}
}
Agora é só uma questão de iniciar a aplicação, fazer um request para o endpoint /long-process
e nesse
meio tempo derrubar a aplicação com um SIGTERM
.
Encontre o id do processo
Quando a aplicação é iniciada você pode encontrar o id do processo nos logs, no meu caso é 6578
.
2018-06-28 20:37:28.292 INFO 6578 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2018-06-28 20:37:28.296 INFO 6578 --- [ main] c.m.wd.gracefulshutdown.Application : Started Application in 2.158 seconds (JVM running for 2.591)
Request e shutdown
Execute um request para o endpoint /long-process
, Eu estou usando curl para isso:
$ curl -i localhost:8080/long-process
Envie um SIGTERM
para o processo:
$ kill 6578
O request que fizemos usando curl
ainda precisa responder como abaixo:
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 14
Date: Thu, 28 Jun 2018 18:39:56 GMT
Process finished
Sumário
Parabéns! Você acabou de aprender como graciosamente desligar uma aplicação spring-boot (eu sei, essa tradução é estranha).
Nota de rodapé
- O código usado nesse tutorial pode ser encontrado no GitHub
- Essa implementação foi baseada nesse comentário